# 10 文件和异常
# 10.1 读取文件
import os
import json
import traceback

print("当前路径: %s" % os.path.abspath('.'))
file_path = 'docs/pi_digits.txt'

print()

# 读取所有
print('读取所有')
# with关键字，使Python会在文件不需要时自动关闭文件。
with open(file_path) as file_obj:
    contents = file_obj.read()
print(contents)

print()

# 逐行读取
print('逐行读取')
with open(file_path) as file_obj:
    for line in file_obj:
        # 文件中每行末尾都会有个换行符
        print(line)

print()

# 取出换行符
print('截掉换行符')
with open(file_path) as file_obj:
    for line in file_obj:
        print(line.rstrip())

print()

# 将文件内容读取到列表中
print('将文件内容读取到列表中')
with open(file_path) as file_obj:
    lines = file_obj.readlines()

print(lines)
for line in lines:
    print(line.rstrip())

print()

# 在pi中寻找自己的生日日期
print('在pi中寻找自己的生日日期')
file_path1 = "docs/pi_digits_10^9.txt"
with open(file_path1) as file_obj:
    lines = file_obj.readlines()

pi_string = ''
for line in lines:
    pi_string += line.rstrip()

# birthday = input('input your birthday with this format yyMMdd: ')
birthday = '930130'
if birthday in pi_string:
    print('your birthday appears in the first million digits of pi!')
else:
    print('your birthday dose not appear in the first million digits of pi!')

print()

# 写入文件
file_name = 'docs/programing.txt'
with open(file_name, 'w+') as file_obj:
    # 'w+' 读写模式：可读写，写入先清空原有内容。文件不存在会创建。
    print(file_obj.readline())  # 输出为空
    file_obj.write("I love Python!")

# 模式：
# 'w' 写入模式：清空再写入，不存在则创建。
# 'r' 只读模式（默认模式）：写入报错，不存在报错。
# 'a' 附加模式：新增写入，不存在则创建。
# 'w+' 读写模式：可读写，写入先清空原有内容。文件不存在会创建。
# 'a+' 读写模式：可读写，从文件顶部读取内容，从文件底部添加内容。文件不存在会创建。
# 'r+' 读写模式：可读写，写入从头覆盖原有内容。
#      如果先读后写，则会在文件最后追加内容。文件不存在会报错。

# 更多 https://www.runoob.com/python/python-func-open.html

# open(name[, mode[, buffering]])

# name : 一个包含了你要访问的文件名称的字符串值。

# mode : mode 决定了打开文件的模式：只读，写入，追加等。
#   所有可取值见如下的完全列表。这个参数是非强制的，默认文件访问模式为只读(r)。

# buffering : 如果 buffering 的值被设为 0，就不会有寄存。
#   如果 buffering 的值取 1，访问文件时会寄存行。
#   如果将 buffering 的值设为大于 1 的整数，表明了这就是的寄存区的缓冲大小。
#   如果取负值，寄存区的缓冲大小则为系统默认。

# t	    文本模式 (默认)。
# x	    写模式，新建一个文件，如果该文件已存在则会报错。
# b	    二进制模式。
# +	    打开一个文件进行更新(可读可写)。
# U 	通用换行模式（不推荐）。
# r	    以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。
# rb	以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。
#       这是默认模式。一般用于非文本文件如图片等。
# r+	打开一个文件用于读写。文件指针将会放在文件的开头。
# rb+	以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。
#       一般用于非文本文件如图片等。
# w	    打开一个文件只用于写入。如果该文件已存在则打开文件，并从开头开始编辑，
#       即原有内容会被删除。如果该文件不存在，创建新文件。
# wb	以二进制格式打开一个文件只用于写入。如果该文件已存在则打开文件，
#       并从开头开始编辑，即原有内容会被删除。如果该文件不存在，创建新文件。
#       一般用于非文本文件如图片等。
# w+	打开一个文件用于读写。如果该文件已存在则打开文件，并从开头开始编辑，
#       即原有内容会被删除。如果该文件不存在，创建新文件。
# wb+	以二进制格式打开一个文件用于读写。如果该文件已存在则打开文件，
#       并从开头开始编辑，即原有内容会被删除。如果该文件不存在，创建新文件。
#       一般用于非文本文件如图片等。
# a	    打开一个文件用于追加。如果该文件已存在，文件指针将会放在文件的结尾。
#       也就是说，新的内容将会被写入到已有内容之后。
#       如果该文件不存在，创建新文件进行写入。
# ab	以二进制格式打开一个文件用于追加。
#       如果该文件已存在，文件指针将会放在文件的结尾。
#       也就是说，新的内容将会被写入到已有内容之后。
#       如果该文件不存在，创建新文件进行写入。
# a+	打开一个文件用于读写。如果该文件已存在，文件指针将会放在文件的结尾。
#       文件打开时会是追加模式。如果该文件不存在，创建新文件用于读写。
# ab+	以二进制格式打开一个文件用于追加。
#       如果该文件已存在，文件指针将会放在文件的结尾。
#       如果该文件不存在，创建新文件用于读写。

# file 对象方法
with open(file_path, 'rb') as file_obj:
    # file.read([size])：size 未指定则返回整个文件，如果文件大小 >2 倍内存则有问题，
    #       f.read()读到文件尾时返回""(空字串)。
    print('size 未指定则返回整个文件:')
    print(file_obj.read())
    # file.readline()：返回一行。
    file_obj.seek(0, 0)
    print('返回一行')
    print(file_obj.readline().rstrip())
    # 设置参数，限定读取的字节数
    print('返回一行,限定读取的字节数')
    print(file_obj.readline(1).rstrip())
    # file.readlines([hint]) ：
    # 1、hint为0或hint为-1：
    #   用于读取所有行(直到结束符 EOF)并返回列表
    # 2、hint为非0和非-1。
    # （1）对于文本数据，字符数量以UNICODE编码字符集的字符为单位计算。
    #       支持多字节的字符，如中文一个汉字表示1字符，空格算一个字符。
    #       对于文本文件，换行符是否计入字符，有如下两种情况：
    #       1）指定字符数中间包含\n，则\n算做字符；
    #       2）如果\n在指定内容末尾，则不计入字符，算作换行符。
    # （2） 在二进制模式下，字符数量是以ASCII码对应的单字节为单位来计算的，
    #       不剔除换行符，字符‘\n’占用一个字节长度；
    # （3） 如果hint大于前n行的总字数但小于前n+1行的总字数，
    #       则执行函数会读取文件的前n+1行；
    print("当前文件指针位置: %s" % file_obj.tell())
    file_obj.seek(0, 0)
    print('返回size行')
    for line in file_obj.readlines(9):
        print(line.rstrip())
# for line in f: print line ：通过迭代器访问。

# f.write("hello\n")：如果要写入字符串以外的数据,先将他转换为字符串。

# f.tell()：返回一个整数,表示当前文件指针的位置(就是到文件头的字节数)。
#   文本模式下，使用next()方法时不能使用tell()
#   for i in f: 迭代器默认会调用f.next()
    print("当前文件指针位置: %s" % file_obj.tell())

# f.seek(偏移量,[起始位置])：用来移动文件指针。
#   偏移量: 单位为字节，可正可负
#   起始位置: 0 - 文件头, 默认值; 1 - 当前位置; 2 - 文件尾
#   文本模式下，起始位置不能设置为1，即当前位置。

# f.close() 关闭文件

print("\n")

# 10.3 异常
# try-except代码块
# 1、ZeroDivisionException 除零异常
try:
    print(5 / 0)
except ZeroDivisionError:
    print("You can't divide by zero")
    print(str(ZeroDivisionError))
    print(repr(ZeroDivisionError))
    print(traceback.print_exc())
    print(traceback.format_exc())

print('=======')

# 2、else代码块，try成功执行才运行的代码块
second_num = 1
try:
    answer = 5 / second_num
except ZeroDivisionError:
    print("You can't divide by zero")
else:
    print("answer: %s" % answer)

print("\n")

# 3、FileNotFoundError 文件不存在异常

# 4、静默失败 pass
print('静默失败')
try:
    print(3 / 0)
except ZeroDivisionError:
    pass
else:
    print("Done")

print()

# 10.4 存储数据 —— json模块
# 10.4.1 json。dump() 和 json.load()
# import json

# 持久化
numbers = [2, 3, 5, 7, 11, 13]
filename = 'docs/numbers.json'
with open(filename, 'w') as f:
    json.dump(numbers, f)

# 加载
with open(filename) as f:
    load_numbers = json.load(f)
print('load from', filename, ':', load_numbers)

print()

# 文件与异常
username_file = 'docs/username.json'


def get_username():
    """获取用户名"""
    try:
        with open(username_file) as f:
            name = json.load(f)
    except FileNotFoundError:
        return None
    else:
        return name


def greet_user():
    """问候用户"""
    username = get_username()
    if username:
        print("Welcome back, %s" % username)
    else:
        name = input("please input your name: ")
        with open(username_file, 'w') as f:
            json.dump(name, f)
        print(f"We'll remember your name when you come back, {name}")


greet_user()
